﻿/* ######################################## *\
 * ### Copyright (C) 2009 AJ Ravindiran ### *
\* ######################################## */
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;

namespace AJRavindiran.Jolt.Network.Tcp
{
    /// <summary>
    /// Represents a tcp connection handler.
    /// </summary>
    public class TcpConnection
    {
        #region Fields
        private HashSet<int> activeConnections;
        private Socket socket;
        private int port, maxConnections, acceptedConnections;
        #endregion Fields

        #region Properties
        /// <summary>
        /// Gets or sets the max ammount of connections 
        /// availible to connect to the server simutaniously.
        /// </summary>
        public int MaxConnections
        {
            get { return maxConnections; }
            set { maxConnections = value; }
        }

        /// <summary>
        /// Gets the ammount of accepted connections since 
        /// the server has started.
        /// </summary>
        public int AcceptedConnections
        {
            get { return acceptedConnections; }
        }

        /// <summary>
        /// Gets if the socket is alive or not.
        /// </summary>
        public bool Alive
        {
            get { return (socket != null); }
        }
        #endregion Properties

        #region Constructors
        /// <summary>
        /// Constructs a new tcp connection class.
        /// </summary>
        /// <param name="port">The port the server will run on.</param>
        /// <param name="maxConnections">The max ammount of connections.</param>
        public TcpConnection(int port, int maxConnections)
        {
            this.port = port;
            this.maxConnections = maxConnections;
            this.Initialize();
        }
        #endregion

        #region Methods
        /// <summary>
        /// Initialize a new tcp connection for the current environment.
        /// </summary>
        /// <returns></returns>
        public void Initialize()
        {
            this.activeConnections = new HashSet<int>();
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                socket.Bind(new IPEndPoint(IPAddress.Any, port));
                socket.Listen(25);
                socket.BeginAccept(new AsyncCallback(HandleRequest), socket);
                Jolt.GetLog().WriteInfo(string.Format("TCP is now listening on port {0}.", port));
            }
            catch (Exception ex)
            {
                Jolt.GetLog().WriteException(ex);
                throw ex;
            }
        }

        /// <summary>
        /// Connection handler for all requests to the ip:port.
        /// </summary>
        private void HandleRequest(IAsyncResult iAr)
        {
            try
            {
                int connectionID = 0;
                // Try getting a connection id that's currently not in use.
                for (int i = 1; i < maxConnections; i++)
                {
                    if (activeConnections.Contains(i) == false)
                    {
                        connectionID = i;
                        break;
                    }
                }

                if (connectionID > 0)
                {
                    Socket connectionSocket = ((Socket)iAr.AsyncState).EndAccept(iAr);
                    string ipAddress = connectionSocket.RemoteEndPoint.ToString().Split(':')[0];
                    acceptedConnections++;
                    LogRequest(ipAddress);
                    Jolt.GetLog().WriteInfo(string.Format("Accepted connection {0} from {1}", connectionID, ipAddress));

                    RuneScape.RuneScape.GetLoginServer().AddToLoginQueue(
                        new RuneScape.Models.Characters.Character(connectionSocket, connectionID, ipAddress));
                }
            }
            catch (Exception ex)
            {
                Jolt.GetLog().WriteException(ex);
            }
            socket.BeginAccept(new AsyncCallback(HandleRequest), socket);
        }

        /// <summary>
        /// Stores any connections requests to the database.
        /// </summary>
        private void LogRequest(string ipAdress)
        {
            using (Storage.DatabaseClient dbClient = Jolt.GetDatabase().GetClient())
            {
                dbClient.AddParamWithValue("date", DateTime.Now.ToString());
                dbClient.AddParamWithValue("address", ipAdress);
                dbClient.ExecuteQuery("INSERT INTO connection_logs (date, ip) VALUES (@date, @address)");
            }
        }

        /// <summary>
        /// Release a connection and flag the id as free.
        /// </summary>
        public void FreeConnection(int connectionID)
        {
            if (activeConnections.Contains(connectionID))
            {
                activeConnections.Remove(connectionID);
                Jolt.GetLog().WriteInfo(string.Format("Flagged connection {0} as free.", connectionID));
            }
        }
        #endregion
    }
}
